import general.*;

import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.RemoteObject;
import java.util.Date;
import java.util.Timer;
import java.util.UUID;
import java.util.Vector;

import net.jini.core.entry.UnusableEntryException;
import net.jini.core.lease.Lease;
import net.jini.core.transaction.TransactionException;
import net.jini.space.JavaSpace;

public abstract class Auctioneer implements ClientAuctioneerInterface {
	protected ProductRequest fReq;
	protected int fStatus;
	protected AuctionTask fRunning;
	protected Vector<RemoteObject> fBidders;
	protected RemoteObject fWinner;
	private Timer fTimer;
	protected JavaSpaceListener fListener;
	private JavaSpace fSpace;

	public abstract String getType();
	protected abstract ProductPrice selectWinner();
	protected abstract void cleanUp();
	protected abstract void newAuction();
	protected abstract void updateBidders();
	public abstract void joinAuction(RemoteObject c, String name, UUID id) throws RemoteException;
	public abstract void placeBid(Bid b) throws RemoteException;

	public Auctioneer() throws IOException {
		fTimer = new Timer();
		fStatus = -1;
		fListener = new JavaSpaceListener();
		connect();
		startSearch();
	}

	private void connect() throws IOException {
		JavaSpaceLookup lookup = new JavaSpaceLookup();

		try {
			fSpace = lookup.getSpace(new String[] { "SEB" });
			System.out.println("Javaspace Discovered");
		} catch (Exception e) {
			System.out.println("No Javaspace Found");
		}

		try {
			fSpace.notify(new ProductRequest(), null, fListener.getStub(), Lease.FOREVER, null);
		} catch (TransactionException e) {
			System.out.println("Notification LEASE not granted");
		}
	}

	private void startSearch() {
		System.out.println("Starting Search");
		while (!fListener.isNotified()) {
			Thread.yield();
		}

		if (fSpace != null) {
			try {
				fReq = (ProductRequest) fSpace.takeIfExists(new ProductRequest(), null, 2000);
				fListener.decreaseNotifications();
				if (fListener.isNotified()) {
					fListener.resetNotifications();
				}
			} catch (RemoteException e) {
				e.printStackTrace();
			} catch (UnusableEntryException e) {
				e.printStackTrace();
			} catch (TransactionException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			try {
				connect();
				startSearch();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if (fReq != null && fRunning == null) {
			if (fReq.fEndTime.after(new Date())) {
				auctionProduct(fReq.fEndTime);
			}
		}
	}

	private void auctionProduct(Date time) {
		newAuction();
		startAuction(time);

		System.out.println("Starting Auction");
		
		while (fRunning != null && fRunning.isRunning()) {
			updateBidders();
			Thread.yield();
		}

		ProductPrice price = selectWinner();
		System.out.println("Auction over => selecting Winner");

		if (fWinner != null) {
			try {
				// Notify Clients auction is over
				notifyBidders();

				// Throw product back in to the pool and clean up
				writeToSpace(price);
			} catch (Exception e) {
				// Winner unreachable. Resell ???
				System.out.println("Error: Winner Unreachable. Starting Resell");
				writeNotSoldToSpace();
			}
		} else {
			System.out.println("No Winner found, writing back to space");
			writeNotSoldToSpace();
		}

		cleanUp();

		// We will get notified if new items to auction enter the space.
		while (!fListener.isNotified()) {
			Thread.yield();
		}

		startSearch();
	}

	public long remainingTime() {
		if (fRunning != null && fRunning.isRunning()) {
			return fRunning.scheduledExecutionTime() - System.currentTimeMillis();
		} else {
			return -1;
		}
	}

	public Product getProduct() {
		return fReq.getProduct();
	}

	private synchronized void startAuction(Date fEndTime) {
		// Start Timer
		fRunning = new AuctionTask();
		fTimer.schedule(fRunning, fEndTime);
	}

	private void notifyBidders() throws Exception {
		if (fWinner != null) {
			try {
				((AuctioneerClientInterface) fWinner).notifyBidders(true, fReq.getProduct());
			} catch (RemoteException e) {
				// Winner Unreachable
				throw new Exception("Winner unreachable");
			}
		}

		for (RemoteObject client : fBidders) {
			try {
				((AuctioneerClientInterface) client).notifyBidders(false, fReq.getProduct());
			} catch (RemoteException e) {
				// Client unreachable
			}
		}
	}

	private void writeNotSoldToSpace() {
		// fSpace.write(fReq, null, Lease.FOREVER);
	}

	private void writeToSpace(ProductPrice price) {
		ProductOrder po = new ProductOrder(fReq.getProduct(), price);
		try {
			fSpace.write(po, null, Lease.FOREVER);
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (TransactionException e) {
			e.printStackTrace();
		}
	}
}